package cn.mntool.fastcode.generate.core;

import cn.mntool.fastcode.generate.config.Properties;
import cn.mntool.fastcode.generate.config.SqlConfig;
import cn.mntool.fastcode.generate.constants.DatabaseType;
import cn.mntool.fastcode.generate.constants.LabelConstants;
import cn.mntool.fastcode.generate.entity.BeanInfo;
import cn.mntool.fastcode.generate.entity.FieldInfo;
import cn.mntool.fastcode.generate.utils.FileUtils;
import cn.mntool.fastcode.generate.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import java.io.*;
import java.net.URL;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * describe: 代码生成核心类
 *
 * @author lianying
 * @date 2020/03/08
 */
@Component
@Slf4j
public class Producer {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Autowired
    private SqlConfig sqlConfig;
    @Autowired
    private Properties properties;
    /**
     * 分隔符
     */
    private String separator = java.io.File.separator;
    /**
     * 根路径
     */
    private String rootPath;
    /**
     * 实体相关信息
     */
    private BeanInfo beanInfo = new BeanInfo();

    /**
     * 开始执行代码生成
     */
    public void start() {
        //获取字段信息sql
        String fieldInfoSql = null;
        //获取表信息sql
        String tableInfoSql = null;
        //获取表主键sql
        String tablePkSql = null;
        switch (properties.getDbType().toLowerCase()) {
            case DatabaseType.MYSQL:
                fieldInfoSql = String.format(sqlConfig.getFieldInfoForMysql(), properties.getTableName(), properties.getDbName());
                tableInfoSql = String.format(sqlConfig.getTableInfoForMysql(), properties.getDbName(), properties.getTableName());
                tablePkSql = String.format(sqlConfig.getTablePkForMysql(), properties.getTableName(), properties.getDbName());
                break;
            case DatabaseType.ORACLE:
                fieldInfoSql = String.format(sqlConfig.getFieldInfoForOracle(), properties.getTableName().toUpperCase(), properties.getTableName().toUpperCase());
                tableInfoSql = String.format(sqlConfig.getTableInfoForOracle(), properties.getTableName().toUpperCase());
                tablePkSql = String.format(sqlConfig.getTablePkForOracle(), properties.getTableName().toUpperCase());
                break;
            default:
                throw new RuntimeException("不支持的数据库类型");
        }
        Map<String, Object> stringObjectMap;
        try {
            stringObjectMap = jdbcTemplate.queryForMap(tableInfoSql);
        }catch (EmptyResultDataAccessException e)
        {
            throw new RuntimeException("没有找到表名称："+properties.getTableName(),e);
        }
        String tableComment = (String) stringObjectMap.get("TABLE_COMMENT");
        Map<String, Object> stringObjectMap2 = jdbcTemplate.queryForMap(tablePkSql);
        Object tablePkObject = stringObjectMap2.get("PK");
        String tablePk;
        if (tablePkObject == null || "".equals(((String) tablePkObject).replace(" ", ""))) {
            log.error("未查找到该表的主键。");
            tablePk = null;
        } else {
            tablePk = (String) tablePkObject;
        }
        //主键设置
        beanInfo.setPkName(tablePk);
        //主键小驼峰java名
        beanInfo.setPkJavaName(StringUtils.lowCamel(tablePk.toLowerCase()));
        //设置表备注
        log.info("获取到表备注：{}", tableComment);
        beanInfo.setTableComment(tableComment);
        List<FieldInfo> fieldInfoList = jdbcTemplate.query(fieldInfoSql, new BeanPropertyRowMapper<>(FieldInfo.class));
        //去除忽略的字段
        Iterator<FieldInfo> iterator = fieldInfoList.iterator();
        while (iterator.hasNext()) {
            FieldInfo next = iterator.next();
            //设置是否主键
            if (tablePk.equalsIgnoreCase(next.getFieldName())) {
                next.setPk(true);
            }
            if (properties.getIgnoreFields().contains(next.getFieldName())) {
                iterator.remove();
            }
        }
        log.info("获取到表字段：{}", fieldInfoList);
        //获取字段对应java类型
        for (int i = 0; i < fieldInfoList.size(); i++) {
            FieldInfo fieldInfo = fieldInfoList.get(i);
            fieldInfo.setDbFieldName(fieldInfoList.get(i).getFieldName());
            //如果是oracle，需要将所有的字段名称变成小写，再转驼峰。
            if (DatabaseType.ORACLE.equals(properties.getDbType().toLowerCase())) {
                fieldInfo.setFieldName(fieldInfo.getFieldName().toLowerCase());
            }
            fieldInfo.setFieldJavaType(StringUtils.toJavaType(properties.getDbType().toLowerCase(), fieldInfo.getDataType()));
        }
        beanInfo.setFieldInfoList(fieldInfoList);
        //文件生成
        try {
            generate();
        } catch (IOException e) {
            throw new RuntimeException("生成文件失败：", e);
        }
    }

    /**
     * 执行生成
     */
    private void generate() throws IOException {
        URL templateUrl = this.getClass().getClassLoader().getResource("template");
        File file = new File(templateUrl.getFile());
        //直接编译运行和jar包运行获取文件夹的方式不一样。
        if (file.exists()) {
            log.info("直接编译运行");
            rootPath = file.getAbsolutePath();
        } else {
            log.info("jar包运行");
            rootPath = file.getAbsolutePath();
            int index = rootPath.indexOf("file:");
            if (index > 0) {
                rootPath = rootPath.substring(0, index) + "template";
            }
        }
        log.info(rootPath);
        File templateFile = new File(rootPath);
        generate(templateFile);
    }

    /**
     * 生成文件
     *
     * @param file
     */
    private void generate(File file) throws IOException {
        if (file.isFile()) {
            generateFile(file);
        } else {
            generateDir(file);
        }
    }

    /**
     * 生成文件
     *
     * @param file
     */
    private void generateFile(File file) throws IOException {
        //做文件生成操作
        String fileName = file.getName();
        String fileContent = FileUtils.getFileContent(file);
        //先进行字段遍历替换
        while (true)
        {
            String fieldString = StringUtils.subStringContent(fileContent, LabelConstants.FIELD_BEGIN, LabelConstants.FIELD_END);
            if (org.apache.commons.lang3.StringUtils.isNoneBlank(fieldString)) {
                StringBuilder stringBuilder = new StringBuilder();
                for (int i = 0; i < beanInfo.getFieldInfoList().size(); i++) {
                    FieldInfo fieldInfo = beanInfo.getFieldInfoList().get(i);
                    if (!fieldInfo.isPk()) {
                        stringBuilder.append(StringUtils.replaceForField(fieldString, fieldInfo));
                    }
                }
                fileContent = fileContent.replace(LabelConstants.FIELD_BEGIN + fieldString + LabelConstants.FIELD_END, stringBuilder.toString());
            }else {
                break;
            }
        }
        while (true)
        {
            String pkFieldString = StringUtils.subStringContent(fileContent, LabelConstants.FIELD_PK_BEGIN, LabelConstants.FIELD_PK_END);
            if (org.apache.commons.lang3.StringUtils.isNoneBlank(pkFieldString)) {
                StringBuilder stringBuilder = new StringBuilder();
                for (int i = 0; i < beanInfo.getFieldInfoList().size(); i++) {
                    FieldInfo fieldInfo = beanInfo.getFieldInfoList().get(i);
                    if (fieldInfo.isPk()) {
                        stringBuilder.append(StringUtils.replaceForField(pkFieldString, fieldInfo));
                    }
                }
                fileContent = fileContent.replace(LabelConstants.FIELD_PK_BEGIN + pkFieldString + LabelConstants.FIELD_PK_END, stringBuilder.toString());
            }else {
                break;
            }
        }
        //替换关键字后的文件内容
        String newFileContent = StringUtils.replaceForClass(fileContent, beanInfo, properties);
        //获取相对路径
        String xdPath = file.getParent().replace(rootPath, "");
        FileUtils.mkdirs(StringUtils.replaceForClass(properties.getOutPath() + separator + xdPath, beanInfo, properties));
        //输出文件
        File outFile = new File(StringUtils.replaceForClass(properties.getOutPath() + separator + xdPath + separator + fileName, beanInfo, properties));
        FileOutputStream fileOutputStream = new FileOutputStream(outFile);
        fileOutputStream.write(newFileContent.getBytes());
        fileOutputStream.flush();
        fileOutputStream.close();
    }

    /**
     * 生成文件夹
     *
     * @param file
     */
    private void generateDir(File file) throws IOException {
        //做文件夹生成操作
        File[] files = file.listFiles();
        for (int i = 0; i < files.length; i++) {
            generate(files[i]);
        }
    }
}
